/* 场景设置工具函数
 *
 * 已支持：
 *  - 天：天空盒，一个图片
 *  - 地：地面，一个材质，几个设置（大小、缩放）
 *  - 雾：雾效，仅设置（颜色、距离范围或强度）
 * 待支持：
 *  - 物体风格：线框颜色、线框不透明度
 * 难支持：
 *  - 光：
 *    - 主光源（平型光，用于发射阴影和照亮顶部）：方向(0-360)、角度(0-90)；颜色(默认白色)、强度(默认1)
 *    - 辅光源（点光源，用于表现材质的高亮效果）
 *    - 半球光（用于照亮底部，同时增强顶部）
 *    - 环境光（用于消除侧面的黑暗）
 *  - 影：开关、范围、辅助器
 *  - 后处理：模糊、景深
 *  - 天气：阴晴、雨雪
 *  - 昼夜：早、中、晚
 *  - 季节：春、夏、秋、冬
 */
// 第三方库
import * as THREE from 'three'
// 项目公用
import { color } from './utils/index'
import { degToRad } from './utils/angle'
import { object3d, layer,cameraU } from "./utils";
import { cachedTextureLoader } from './CachedTextureLoader'
import type {
  SceneJson,
  SceneEnvConfig,
  SceneSkyConfig,
  SceneGroundConfig,
  SceneFogConfig,
  SceneObjectStyleConfig,
  SceneNodeData,
  SceneObjectStyleMaterials,
} from './types'
import { isObjectModel, isObjectSceneNode } from './utils/object-op'
// 当前页面
import { materialLoader } from './MaterialLoader'
import { defaultGroundMaterialConfig } from './MaterialLoader'
import defaultEnvMap from './assets/envmap/cloudy_sky.jpg'

export { defaultEnvMap }

// MARK: 整数 -------------------------------------------------------------------------------------

// 保留0位小数
export function r0(n: number) {
  return Math.round(n)
}
// 保留2位小数
export function r1(n: number) {
  return Math.round(n * 10) / 10
}
// 保留2位小数
export function r2(n: number) {
  return Math.round(n * 100) / 100
}
// 保留3位小数
export function r3(n: number) {
  return Math.round(n * 1000) / 1000
}
// 保留4位小数
export function r4(n: number) {
  return Math.round(n * 10000) / 10000
}
// 保留5位小数
export function r5(n: number) {
  return Math.round(n * 100000) / 100000
}

// MARK: 通用 -------------------------------------------------------------------------------------

/** 默认场景天空设置 */
export const defaultSceneSkyConfig: SceneSkyConfig = {
  type: 'ENVMAP',
  envMap: '',
  color: [1.0, 1.0, 1.0],
}

/** 默认场景地面设置 */
export const defaultSceneGroundConfig: SceneGroundConfig = {
  materialId: 'DEFAULT_GROUND',
  materialPreviewUrl: defaultGroundMaterialConfig.previewImage as string,
  groundSize: 512,
  textureScale: 4,
}

/** 默认场景雾气设置 */
export const defaultSceneFogConfig: SceneFogConfig = {
  type: 'NONE',
  color: [1.0, 1.0, 1.0],
  near: 10,
  far: 100,
  density: 0.01,
}

/** 默认场景物体风格设置 */
export const defaultSceneObjectStyleConfig: SceneObjectStyleConfig = {
  type: 'REALISTIC',
  frameColor: [0.0, 0.4, 1.0], // 蓝色
  surfaceColor: [0.0, 0.4, 1.0], // 蓝色
  surfaceOpacity: 0.5,
}

/**
 * 默认的场景设置
 */
export const defaultSceneEnvConfig: SceneEnvConfig = {
  sky: defaultSceneSkyConfig,
  fog: defaultSceneFogConfig,
  ground: defaultSceneGroundConfig,
  objectStyle: defaultSceneObjectStyleConfig,
}

/**
 * 填充可能缺失的场景设置字段`
 * @param sceneConfig 场景设置
 */
export function fillSceneConfigFields(sceneConfig: SceneJson) {
  // environment: 场景环境设置
  if (sceneConfig.environment == null) {
    sceneConfig.environment = defaultSceneEnvConfig
  }

  // sky: 天空设置
  if (sceneConfig.environment.sky == null) {
    sceneConfig.environment.sky = defaultSceneSkyConfig
  }

  // ground: 地面设置
  if (sceneConfig.environment.ground == null) {
    sceneConfig.environment.ground = defaultSceneGroundConfig
  }

  // fog: 雾效设置
  if (sceneConfig.environment.fog == null) {
    sceneConfig.environment.fog = defaultSceneFogConfig
  }

  // objectStyle: 物体风格设置
  if (sceneConfig.environment.objectStyle == null) {
    sceneConfig.environment.objectStyle = defaultSceneObjectStyleConfig
  }
}

/**
 * 为场景应用默认场景设置
 * @param scene 目标场景
 */
export async function applyDefaultSceneConfig(scene: THREE.Scene) {
  applySceneSkyConfig(scene, defaultSceneSkyConfig) // 天空
  await applySceneGroundConfig(scene, defaultSceneGroundConfig) // 地面
  applySceneFogConfig(scene, defaultSceneFogConfig) // 雾效
}

/**
 * 为场景应用默认场景设置，并且克隆材质
 * @param scene 目标场景
 */
export async function applyDefaultSceneConfigAndCloneMaterial(scene: THREE.Scene) {
  applySceneSkyConfig(scene, defaultSceneSkyConfig) // 天空
  await applySceneGroundConfig(scene, defaultSceneGroundConfig) // 地面
  // 临时App的地面材质需要克隆，否则当这个 App 清理时，会把原编辑器的材质也清理掉
  // 为什么会？：因为有材质缓存，所以不克隆的话，它们的地面材质会是同一个
  const groundMesh = getOrCreateGroundMesh(scene)
  groundMesh.material = cloneGroundMaterial(groundMesh.material as THREE.MeshStandardMaterial)
}

// MARK: 物体风格 ---------------------------------------------------------------------------------

/**
 * 转换物体风格为两种材质
 * @param objectStyle 目标物体风格
 * @returns 转换为的两种材质（线框和表面）
 */
export function objectStyleToMaterial(objectStyle: SceneObjectStyleConfig) {
  const frameMaterial = new THREE.MeshBasicMaterial({
    color: new THREE.Color(color.normRgbToHexNum(objectStyle.frameColor)),
  })
  const surfaceMaterial = new THREE.MeshBasicMaterial({
    color: new THREE.Color(color.normRgbToHexNum(objectStyle.surfaceColor)),
    transparent: true,
    opacity: objectStyle.surfaceOpacity,
  })
  return {
    frameMaterial,
    surfaceMaterial,
  }
}

/**
 * 应用物体风格到物体（仅模型物体，非组，不在此处递归）
 * @param objectStyle 目标物体风格设置
 * @param object 目标物体
 */
export function applyObjectMaterialsToObject(materials: SceneObjectStyleMaterials, targetObject: THREE.Object3D) {
  const userData = targetObject.userData as SceneNodeData
  if (userData.isModel) {
    // 模型物体：生成线框
    if (!userData.isFrameGenerated) {
      generateFrame(targetObject, materials.frameMaterial)
    }

    // 设置表面材质
    object3d.traverseWhen(
      targetObject,
      (_node: THREE.Object3D) => {
        const node = _node as THREE.Mesh
        if (node.isMesh) {
          node.material = materials.surfaceMaterial
        }
      },
      (node: THREE.Object3D) => {
        // 条件：是物体节点且是模型物体，或者不是物体节点
        const isModelObject = isObjectModel(node)
        const isNotNode = !isObjectSceneNode(node)
        return isModelObject || isNotNode
      },
    )
  }
}

/**
 * 为模型物体生成线框
 * @param modelObject 目标模型物体
 * @param frameMaterial 目标材质
 */
function generateFrame(modelObject: THREE.Object3D, frameMaterial: THREE.MeshBasicMaterial) {
  object3d.traverseWhen(
    modelObject,
    (_node: THREE.Object3D) => {
      const node = _node as THREE.Mesh
      if (node.isMesh) {
        const edgesGeometry = new THREE.EdgesGeometry(node.geometry)
        const frame = new THREE.LineSegments(edgesGeometry, frameMaterial)
        frame.position.copy(node.position)
        frame.scale.copy(node.scale)
        frame.rotation.copy(node.rotation)
        node.parent!.add(frame)
      }
    },
    (node: THREE.Object3D) => {
      // 条件：是物体节点且是模型物体，或者不是物体节点
      const isModelObject = isObjectModel(node)
      const isNotNode = !isObjectSceneNode(node)
      return isModelObject || isNotNode
    },
  )

  // 修改标记
  const userData = modelObject.userData as SceneNodeData
  userData.isFrameGenerated = true
}

// MARK: 天空 -------------------------------------------------------------------------------------

/**
 * 为场景设置环境贴图
 * @param scene 目标场景
 * @param envMapUrl 环境贴图URL
 */
export function applySceneSkyConfig(scene: THREE.Scene, skyConfig: SceneSkyConfig) {
  const targetEnvMap = skyConfig.envMap || defaultEnvMap
  const envTexture = cachedTextureLoader.loadEnvSync(targetEnvMap)!
  scene.background = envTexture
  scene.environment = envTexture

  if (skyConfig.type == 'ENVMAP') {
    // 环境贴图
    const envTexture = cachedTextureLoader.loadEnvSync(targetEnvMap)
    scene.background = envTexture
    scene.environment = envTexture
  } else {
    // 纯色
    scene.background = new THREE.Color(color.normRgbToHexNum(skyConfig.color))
    scene.environment = cachedTextureLoader.loadEnvSync(defaultEnvMap)
  }
}

// MARK: 地面 -------------------------------------------------------------------------------------

/**
 * 为场景设置地面材质
 * 基础大小（planeSize = 0)：512m
 * @param scene 目标场景
 * @param materialId 环境贴图URL
 */
export async function applySceneGroundConfig(scene: THREE.Scene, groundConfig: SceneGroundConfig) {
  let materialId = groundConfig.materialId
  if (materialId == '') materialId = 'DEFAULT_GROUND'

  const groundMesh = getOrCreateGroundMesh(scene) as THREE.Mesh
  groundMesh.material = await loadGroundMaterialWithCache(materialId)
  groundMesh.scale.set(groundConfig.groundSize / 512, groundConfig.groundSize / 512, 1)
  const textureScale = groundConfig.textureScale / (groundConfig.groundSize / 512)
  setGroundTextureScale(groundMesh.material as THREE.MeshStandardMaterial, textureScale)
}

/**
 * 获取或创建地面网格体
 * @param scene 目标场景
 * @returns 地面网格体
 */
export function getOrCreateGroundMesh(scene: THREE.Scene) {
  const foundGroundGroup = scene.children.find((item) => item.name == 'JaxGround')
  if (foundGroundGroup != null) return foundGroundGroup.children[0] as THREE.Mesh

  const groundGroup = new THREE.Group()
  groundGroup.name = 'JaxGround'
  scene.add(groundGroup)

  const planeMaterial = new THREE.MeshStandardMaterial()
  const planeGeometry = new THREE.PlaneGeometry(512, 512)
  const plane = new THREE.Mesh(planeGeometry, planeMaterial)
  // plane.position.y = -0.01 // 往下移动 1cm，阻止它与坐标轴重叠闪烁
  plane.rotation.x = degToRad(-90)
  plane.rotation.z = degToRad(180)
  plane.receiveShadow = true
  plane.name = 'Plane'
  groundGroup.add(plane)

  // 设置地面为可拾取
  layer.enable(groundGroup, 2)

  return plane
}

/**
 * 地面材质缓存
 */
const groundMaterialCache: { [materialId: string]: THREE.MeshStandardMaterial } = {}

/**
 * 清空地面材质缓存
 */
export function cleanGroundMaterialCache() {
  Object.keys(groundMaterialCache).forEach(function (prop) {
    delete groundMaterialCache[prop]
  })
}

/**
 * 加载地面材质（带缓存）
 * @param materialId 材质ID
 */
function loadGroundMaterialWithCache(materialId: string): Promise<THREE.MeshStandardMaterial> {
  // console.log('loadGroundMaterialWithCache', materialId)
  return new Promise((resolve, reject) => {
    if (groundMaterialCache[materialId]) {
      resolve(groundMaterialCache[materialId])
    } else {
      const material = materialLoader.load(materialId, () => {
        const clonedMaterial = cloneGroundMaterial(material)
        groundMaterialCache[materialId] = clonedMaterial
        resolve(clonedMaterial)
      })
    }
  })
}

/**
 * 从基础材质创建地面材质：克隆、重复、包装
 * @param baseMaterial 基础材质
 * @param repeat 重复次数
 * @returns 新的地面材质
 */
export function cloneGroundMaterial(baseMaterial: THREE.MeshStandardMaterial) {
  // 克隆材质
  const clonedMaterial = baseMaterial.clone()
  // 贴图：克隆、重复、包装
  if (clonedMaterial.map) {
    clonedMaterial.map = clonedMaterial.map.clone()
    clonedMaterial.map.wrapS = clonedMaterial.map.wrapT = THREE.RepeatWrapping
  }
  if (clonedMaterial.roughnessMap) {
    clonedMaterial.roughnessMap = clonedMaterial.roughnessMap.clone()
    clonedMaterial.roughnessMap.wrapS = clonedMaterial.roughnessMap.wrapT = THREE.RepeatWrapping
  }
  if (clonedMaterial.metalnessMap) {
    clonedMaterial.metalnessMap = clonedMaterial.metalnessMap.clone()
    clonedMaterial.metalnessMap.wrapS = clonedMaterial.metalnessMap.wrapT = THREE.RepeatWrapping
  }
  if (clonedMaterial.normalMap) {
    clonedMaterial.normalMap = clonedMaterial.normalMap.clone()
    clonedMaterial.normalMap.wrapS = clonedMaterial.normalMap.wrapT = THREE.RepeatWrapping
  }
  if (clonedMaterial.aoMap) {
    clonedMaterial.aoMap = clonedMaterial.aoMap.clone()
    clonedMaterial.aoMap.wrapS = clonedMaterial.aoMap.wrapT = THREE.RepeatWrapping
  }
  if (clonedMaterial.emissiveMap) {
    clonedMaterial.emissiveMap = clonedMaterial.emissiveMap.clone()
    clonedMaterial.emissiveMap.wrapS = clonedMaterial.emissiveMap.wrapT = THREE.RepeatWrapping
  }
  // 标记更新
  clonedMaterial.needsUpdate = true
  return clonedMaterial
}

/**
 * 设置地面纹理缩放
 * @param material 目标材质
 * @param scale 缩放比例
 */
function setGroundTextureScale(material: THREE.MeshStandardMaterial, scale: number) {
  const repeat = 512 / scale
  // 贴图重复
  if (material.map) {
    material.map.repeat.set(repeat, repeat)
  }
  if (material.roughnessMap) {
    material.roughnessMap.repeat.set(repeat, repeat)
  }
  if (material.metalnessMap) {
    material.metalnessMap.repeat.set(repeat, repeat)
  }
  if (material.normalMap) {
    material.normalMap.repeat.set(repeat, repeat)
  }
  if (material.aoMap) {
    material.aoMap.repeat.set(repeat, repeat)
  }
  if (material.emissiveMap) {
    material.emissiveMap.repeat.set(repeat, repeat)
  }
  // 标记更新
  material.needsUpdate = true
}

/**
 * 让地面永远在视野中心，而且对齐到 128m，防止滑动
 * @param scene 目标场景
 * @param camera 目标相机
 */
export function makeGroundAtCameraCenter(scene: THREE.Scene, camera: THREE.PerspectiveCamera) {
  const foundGroundGroup = scene.children.find((item) => item.name == 'JaxGround')
  if (foundGroundGroup) {
    const cameraFocusGroundPos = cameraU.getFocusAtGround(camera)
    if (cameraFocusGroundPos) {
      foundGroundGroup.position.x = Math.round(cameraFocusGroundPos.x / 128) * 128
      foundGroundGroup.position.z = Math.round(cameraFocusGroundPos.z / 128) * 128
    }
  }
}

// MARK: 雾效 -------------------------------------------------------------------------------------

/**
 * 为场景设置雾效
 * @param scene 目标场景
 * @param fogConfig 雾效设置
 */
export function applySceneFogConfig(scene: THREE.Scene, fogConfig: SceneFogConfig) {
  const colorHex = color.normRgbToHexNum(fogConfig.color)
  if (fogConfig.type == 'NONE') {
    scene.fog = null
  } else if (fogConfig.type == 'LINEAR') {
    scene.fog = new THREE.Fog(colorHex, fogConfig.near, fogConfig.far)
  } else if (fogConfig.type == 'EXP') {
    scene.fog = new THREE.FogExp2(colorHex, fogConfig.density)
  }
}

// 将数值范围 [0 - 100] 映射到距离范围 [10m - 10km]，保留整数
export function linearDistValToLabel(v: number) {
  return r0(Math.pow(1.0715193, v) * 10)
}
export function linearDistLabelToVal(l: number) {
  return r0(Math.log(l / 10) / Math.log(1.0715193))
}

// 将数值范围 [1 - 100] 映射到指数雾浓度 [0.001 - 0.01]，保留3位小数
export function expDensityValToLabel(v: number) {
  return r0(Math.log(v * 1000) / Math.log(1.04712855))
}
export function expDensityLabelToVal(l: number) {
  return r3(Math.pow(1.04712855, l) / 1000)
}
